#include <QtGui>
#include <QtWebKit/QWebFrame>
#include <iostream>
#include <boost/date_time/posix_time/posix_time.hpp>

#include "geometric_main_window.h"
#include "geometric_problem.h"
#include "Log.h"
#include "kdtree.h"
#include "covertree.h"
#include "auction_matching_kdtree.h"

MainWindow::MainWindow(const QString& windowName, QWidget *parent)
  : QMainWindow(parent)
{
	this->setWindowTitle(windowName);
	createWidgets();
	createActions();
	createToolbar();
	createLayout();
	connectSignals();
}

MainWindow::~MainWindow()
{
}

void MainWindow::createWidgets()
{
  m_drawArea = new DrawArea(this);

  m_problem << new PolygonMidpoint << new ConvexHull
            << new KDTreeBuild << new CoverTreeBuild
            << new Matching <<  new AuctionMatching
            << new AuctionMatchingKDTree;
  m_currentProblem = m_problem[0];
  m_cmbProblem = new QComboBox;
  for (int i = 0; i < m_problem.size(); i++) {
    m_cmbProblem->addItem(QString::fromStdString(m_problem[i]->name()), i);
  }

  m_edtXMIN = new QLineEdit("-20");
  m_edtXMAX = new QLineEdit("20");
  m_edtYMIN = new QLineEdit("-20");
  m_edtYMAX = new QLineEdit("20");
  m_edtKNN  = new QLineEdit("2");
}

void MainWindow::createActions() 
{
  m_actLoadData = new QAction("Load data", this); m_actLoadData->setShortcut(Qt::Key_F3);
  m_actSolve = new QAction("Solve", this); m_actSolve->setShortcut(Qt::Key_F5);
  m_actFindNN = new QAction("Find nearest neighbor", this); m_actFindNN->setShortcut(Qt::Key_F6);
  m_actFindKNN = new QAction("Find k nearest neighbors", this); m_actFindKNN->setShortcut(Qt::Key_F7);
  m_actGenerateData = new QAction("Generate data", this); m_actGenerateData->setShortcut(Qt::Key_F2);
}

void MainWindow::createToolbar() 
{
  m_tbTools = addToolBar("Tools");
  m_tbTools->addAction(m_actGenerateData);
  m_tbTools->addAction(m_actLoadData);
  m_tbTools->addAction(m_actSolve);
  m_tbTools->addAction(m_actFindNN);
  m_tbTools->addAction(m_actFindKNN);
  m_tbTools->addWidget(m_cmbProblem);

  addToolBarBreak();
  m_tbPlotBox = addToolBar("PlotBox");
  m_tbPlotBox->addWidget(new QLabel("xmin")); m_tbPlotBox->addWidget(m_edtXMIN); 
  m_tbPlotBox->addWidget(new QLabel("xmax")); m_tbPlotBox->addWidget(m_edtXMAX); 
  m_tbPlotBox->addWidget(new QLabel("ymin")); m_tbPlotBox->addWidget(m_edtYMIN); 
  m_tbPlotBox->addWidget(new QLabel("ymax")); m_tbPlotBox->addWidget(m_edtYMAX); 
  m_tbPlotBox->addWidget(new QLabel("knn")); m_tbPlotBox->addWidget(m_edtKNN); 
}

void MainWindow::createLayout()
{
  setCentralWidget(m_drawArea);
}

void MainWindow::connectSignals()
{
  //connect(this, SIGNAL(  ), this, SLOT(  ));
  connect(m_cmbProblem, SIGNAL(	currentIndexChanged(int) ), this, SLOT( slot_problemChanged(int) ));
  connect(m_actGenerateData, SIGNAL( triggered() ), this, SLOT( slot_generateData() ));
  connect(m_actLoadData, SIGNAL( triggered() ), this, SLOT( slot_loadData() ));
  connect(m_actSolve, SIGNAL( triggered() ), this, SLOT( slot_solve() ));
  connect(m_actFindNN, SIGNAL( triggered() ), this, SLOT( slot_findNN() ));
  connect(m_actFindKNN, SIGNAL( triggered() ), this, SLOT( slot_findKNN() ));
  connect(m_edtXMIN, SIGNAL( returnPressed() ), m_drawArea, SLOT( update() ));
  connect(m_edtXMAX, SIGNAL( returnPressed() ), m_drawArea, SLOT( update() ));
  connect(m_edtYMIN, SIGNAL( returnPressed() ), m_drawArea, SLOT( update() ));
  connect(m_edtYMAX, SIGNAL( returnPressed() ), m_drawArea, SLOT( update() ));
}

// =========== Helper functions ================

void MainWindow::draw(QWidget* drawArea)
{
  Plotter pl(drawArea);
  preparePlotter(pl);

  if (m_currentProblem)
    m_currentProblem->draw(pl);

  QPen pen(Qt::SolidLine);
  pen.setColor(Qt::blue);
  pen.setWidth(2);
  pl.setPen(pen);
  pl.drawEllipse(m_drawArea->currentPoint(), 3.0, 3.0);

  pen.setColor(Qt::green);
  pl.setPen(pen);
  for (unsigned int i = 0; i < m_kNN.size(); i++)
    pl.drawEllipse(pl.point(m_kNN[i]), 4.0, 4.0);
}

void MainWindow::preparePlotter(Plotter& pl)
{
  bool okxmin, okxmax, okymin, okymax;
  double xmin = m_edtXMIN->text().toDouble(&okxmin);
  double xmax = m_edtXMAX->text().toDouble(&okxmax);
  double ymin = m_edtYMIN->text().toDouble(&okymin);
  double ymax = m_edtYMAX->text().toDouble(&okymax);
  if (okxmin && okxmax && okymin && okymax) {
  }
  else {
    xmin = -10; ymin = -10;
    xmax = 10; ymax = 10;
  }
  pl.setBoundary(xmin, xmax, ymin, ymax);
}

void MainWindow::prepareAxis(Axis& axis)
{
  bool okxmin, okxmax, okymin, okymax;
  double xmin = m_edtXMIN->text().toDouble(&okxmin);
  double xmax = m_edtXMAX->text().toDouble(&okxmax);
  double ymin = m_edtYMIN->text().toDouble(&okymin);
  double ymax = m_edtYMAX->text().toDouble(&okymax);
  if (okxmin && okxmax && okymin && okymax) {
  }
  else {
    xmin = -10; ymin = -10;
    xmax = 10; ymax = 10;
  }
  axis.setBoundary(xmin, xmax, ymin, ymax);
}

// ================ KEY EVENT ==============
void MainWindow::keyPressEvent(QKeyEvent *e) 
{
  Log::m_log << "Key pressed: key = " << dec << e->key()  << " modifier = 0x" << hex << e->modifiers() << dec << endl;
  if (e->key() == Qt::Key_T && e->modifiers() == Qt::ControlModifier)         // Ctrl+T new tab
    ;
  else
    QMainWindow::keyPressEvent(e);
}

// ================ SLOTS =====================
void MainWindow::slot_loadData()
{
  QString fName = QFileDialog::getOpenFileName(this, "Choose data file ..."), fName2;
  if (!fName.isEmpty()) {
    Log::m_log << "Load data from " << fName << " ... ";
    if (m_currentProblem == m_problem.at(4) ||
        m_currentProblem == m_problem.at(5) ||
        m_currentProblem == m_problem.at(6)) {
      fName2 = QFileDialog::getOpenFileName(this, "Choose second data file ...");
      static_cast<Matching*>(m_currentProblem)->loadData(fName.toStdString(), fName2.toStdString());
    }
    else
      m_currentProblem->loadData(fName.toStdString());
    Log::m_log << "done." << endl;
  }
  m_drawArea->update();
}

void MainWindow::slot_generateData()
{
  QString fName = QFileDialog::getSaveFileName(this, "Choose data file to generate...");
  if (!fName.isEmpty()) {
    bool ok;
    int n = QInputDialog::getInt(this, "Number of points", "Enter number of points", 1000, 1, 1000000, 100, &ok);
    if (!ok) return;
    int d = QInputDialog::getInt(this, "Number of dimensions", "Enter number of dimensions", 2, 2, 1000, 10, &ok);
    if (!ok) return;

    std::ofstream os(fName.toStdString().c_str());
    os << n << " " << d << std::endl;
    std::vector<vec> v(n, vec(d));
    vec v5(d); v5.randn(); v5 *= 10;
    std::cout << "v5=\n" << v5 << std::endl;
    for (int i = 0; i < n; i++) {
      v[i].randn();
      v[i] *= 4;
      v[i] -= v5;
      v[i].save(os, arma::arma_ascii);
    }
    os.close();
    Log::m_log << fName << " saved n=" << n << " d=" << d << endl;
  }
}

void MainWindow::slot_solve()
{
  boost::posix_time::ptime time_start(boost::posix_time::microsec_clock::local_time());
  m_currentProblem->solve();
  boost::posix_time::ptime time_end(boost::posix_time::microsec_clock::local_time());
  boost::posix_time::time_duration duration(time_end - time_start);
  std::cerr << "Duration = " << duration << "\n";

  m_drawArea->update();
}

template <typename P, typename T> 
bool findNN(Problem* prob, const QPointF& queryPoint, vec& ret_val)
{
  P* cast_p = static_cast<P*>(prob);
  T* tree = cast_p->tree();
  if (tree) {
    vec q(tree->n_dim()); q[0] = queryPoint.x(); q[1] = queryPoint.y();
    int k = 1;
    std::vector<int> knnIdx(k, -1);
    std::vector<double> minBound(k, POSITIVE_INFINITY);
    tree->findKNN(q, knnIdx, minBound);
    ret_val = tree->get(knnIdx[0]);
    return true;
  }
  else {
    Log::m_log << "tree not initialized" << endl;
    return false;
  }
}

template <typename P, typename T> 
bool findKNN(Problem* prob, const QPointF& queryPoint, std::vector<vec>& kNN)
{
  P* cast_p = static_cast<P*>(prob);
  T* tree = cast_p->tree();
  if (tree) {
    vec q(tree->n_dim()); q[0] = queryPoint.x(); q[1] = queryPoint.y();
    int k = kNN.size();
    std::vector<int> knnIdx(k, -1);
    std::vector<double> minBound(k, POSITIVE_INFINITY);
    tree->findKNN(q, knnIdx, minBound);
    for (int i = 0; i < k; i++) if (knnIdx[i] != -1)
      kNN[i] = tree->get(knnIdx[i]);
    return true;
  }
  else {
    Log::m_log << "tree not initialized" << endl;
    return false;
  }
}

void MainWindow::slot_findNN()
{
  Axis axis(m_drawArea);
  prepareAxis(axis);
  QPointF queryPoint = axis.reversePoint(m_drawArea->currentPoint());
  Log::m_log << "queryPoint x=" << queryPoint.x() << " y=" << queryPoint.y() << endl;
  if (m_currentProblem == m_problem.at(2)) {// kdtree
    m_kNN = std::vector<vec>(1);
    bool ok = findNN<KDTreeBuild, KDTree>(m_currentProblem, queryPoint, m_kNN[0]);
    if (!ok) m_kNN.clear();
  }
  m_drawArea->update();
}

void MainWindow::slot_findKNN()
{
  Axis axis(m_drawArea);
  prepareAxis(axis);
  QPointF queryPoint = axis.reversePoint(m_drawArea->currentPoint());
  int k = m_edtKNN->text().toUInt();
  Log::m_log << "queryPoint x=" << queryPoint.x() << " y=" << queryPoint.y() << endl;

  m_kNN = std::vector<vec>(k, vec());
  bool ok = false;
  if (m_currentProblem == m_problem.at(2)) // kdtree
    ok = findKNN<KDTreeBuild, KDTree>(m_currentProblem, queryPoint, m_kNN);
  else if (m_currentProblem == m_problem.at(3)) // covertree
    ok = findKNN<CoverTreeBuild, CoverTree>(m_currentProblem, queryPoint, m_kNN);
  if (!ok) m_kNN.clear();

  m_drawArea->update();
}

void MainWindow::slot_problemChanged(int index)
{
  int pIdx = m_cmbProblem->itemData(index).toInt();
  Log::m_log << "Chosen: " << QString::fromStdString(m_problem[pIdx]->name()) << endl;
  m_currentProblem = m_problem[pIdx];
  m_kNN.clear();
  m_drawArea->set_currentPoint(QPointF(0,0));
  m_drawArea->update();
}

// =================== DrawArea ========================
void DrawArea::paintEvent(QPaintEvent *e) 
{
  m_mainWindow->draw(this);
}

void DrawArea::mouseDoubleClickEvent(QMouseEvent *e)
{
  m_currentPoint = e->posF();
  update();
  Log::m_log << "double clicked x=" << m_currentPoint.x() << " y=" << m_currentPoint.y() << endl;
}
